Añade soporte para la generación y renderización de códigos QR en PDFs#1769
Añade soporte para la generación y renderización de códigos QR en PDFs#1769NeoRazorX merged 1 commit intoNeoRazorX:masterfrom
Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR adds support for generating and rendering QR codes in PDF documents. The changes allow for QR images to be displayed in both the header and after document lines, with proper positioning and layout management.
- Adds QR code rendering functionality with support for base64 and file path images
- Implements a flexible layout system that adjusts header space allocation based on QR presence (80%/20% split when QR exists)
- Includes text wrapping capabilities for QR titles with automatic line breaking
Comments suppressed due to low confidence (1)
Core/Lib/PDF/PDFDocument.php:734
- The str_starts_with() function was introduced in PHP 8.0. If this project needs to support PHP versions prior to 8.0, use substr($qrImage, 0, 11) === 'data:image/' instead.
if (str_starts_with($qrImage, 'data:image/')) {
| try { | ||
| // Usar la función nativa de Cezpdf para añadir la imagen con dimensiones cuadradas | ||
| if ($mimeType === 'image/png') { | ||
| $this->pdf->addPngFromFile($tempFile, $qrX, $qrY - $qrSize, $qrSize, $qrSize); |
There was a problem hiding this comment.
The catch block silently returns without any error logging or user feedback when image addition fails. Consider adding error logging or a fallback mechanism to inform users when QR code rendering fails.
| $words = explode(' ', $qrTitle); | ||
| $lines = []; | ||
| $currentLine = ''; | ||
|
|
||
| // Si no hay espacios en el texto (es una sola "palabra"), dividir por caracteres | ||
| if (count($words) === 1) { | ||
| $text = $qrTitle; | ||
| $currentLine = ''; | ||
|
|
||
| for ($i = 0; $i < strlen($text); $i++) { | ||
| $char = $text[$i]; | ||
| $testLine = $currentLine . $char; | ||
| $testWidth = $this->pdf->getTextWidth(self::FONT_SIZE, $testLine); | ||
|
|
||
| if ($testWidth <= $availableTextWidth) { | ||
| $currentLine = $testLine; | ||
| } else { | ||
| if (!empty($currentLine)) { | ||
| $lines[] = $currentLine; | ||
| $currentLine = $char; | ||
| } else { | ||
| // Si un solo carácter no cabe, lo añadimos anyway | ||
| $lines[] = $char; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Añadir la última línea si no está vacía | ||
| if (!empty($currentLine)) { | ||
| $lines[] = $currentLine; | ||
| } | ||
| } else { | ||
| // Hay espacios, dividir por palabras como antes | ||
| foreach ($words as $word) { | ||
| $testLine = empty($currentLine) ? $word : $currentLine . ' ' . $word; | ||
| $testWidth = $this->pdf->getTextWidth(self::FONT_SIZE, $testLine); | ||
|
|
||
| if ($testWidth <= $availableTextWidth) { | ||
| $currentLine = $testLine; | ||
| } else { | ||
| if (!empty($currentLine)) { | ||
| $lines[] = $currentLine; | ||
| $currentLine = $word; | ||
| } else { | ||
| // La palabra sola es demasiado larga, dividir por caracteres | ||
| $currentLine = ''; | ||
| for ($i = 0; $i < strlen($word); $i++) { | ||
| $char = $word[$i]; | ||
| $testLine = $currentLine . $char; | ||
| $testWidth = $this->pdf->getTextWidth(self::FONT_SIZE, $testLine); | ||
|
|
||
| if ($testWidth <= $availableTextWidth) { | ||
| $currentLine = $testLine; | ||
| } else { | ||
| if (!empty($currentLine)) { | ||
| $lines[] = $currentLine; | ||
| $currentLine = $char; | ||
| } else { | ||
| $lines[] = $char; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Añadir la última línea si no está vacía | ||
| if (!empty($currentLine)) { | ||
| $lines[] = $currentLine; | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
The text wrapping logic in renderQRtitle() is overly complex with nested conditions and duplicated character-by-character splitting logic. Consider extracting this into a separate method like wrapTextToLines() to improve readability and maintainability.
| $words = explode(' ', $qrTitle); | |
| $lines = []; | |
| $currentLine = ''; | |
| // Si no hay espacios en el texto (es una sola "palabra"), dividir por caracteres | |
| if (count($words) === 1) { | |
| $text = $qrTitle; | |
| $currentLine = ''; | |
| for ($i = 0; $i < strlen($text); $i++) { | |
| $char = $text[$i]; | |
| $testLine = $currentLine . $char; | |
| $testWidth = $this->pdf->getTextWidth(self::FONT_SIZE, $testLine); | |
| if ($testWidth <= $availableTextWidth) { | |
| $currentLine = $testLine; | |
| } else { | |
| if (!empty($currentLine)) { | |
| $lines[] = $currentLine; | |
| $currentLine = $char; | |
| } else { | |
| // Si un solo carácter no cabe, lo añadimos anyway | |
| $lines[] = $char; | |
| } | |
| } | |
| } | |
| // Añadir la última línea si no está vacía | |
| if (!empty($currentLine)) { | |
| $lines[] = $currentLine; | |
| } | |
| } else { | |
| // Hay espacios, dividir por palabras como antes | |
| foreach ($words as $word) { | |
| $testLine = empty($currentLine) ? $word : $currentLine . ' ' . $word; | |
| $testWidth = $this->pdf->getTextWidth(self::FONT_SIZE, $testLine); | |
| if ($testWidth <= $availableTextWidth) { | |
| $currentLine = $testLine; | |
| } else { | |
| if (!empty($currentLine)) { | |
| $lines[] = $currentLine; | |
| $currentLine = $word; | |
| } else { | |
| // La palabra sola es demasiado larga, dividir por caracteres | |
| $currentLine = ''; | |
| for ($i = 0; $i < strlen($word); $i++) { | |
| $char = $word[$i]; | |
| $testLine = $currentLine . $char; | |
| $testWidth = $this->pdf->getTextWidth(self::FONT_SIZE, $testLine); | |
| if ($testWidth <= $availableTextWidth) { | |
| $currentLine = $testLine; | |
| } else { | |
| if (!empty($currentLine)) { | |
| $lines[] = $currentLine; | |
| $currentLine = $char; | |
| } else { | |
| $lines[] = $char; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Añadir la última línea si no está vacía | |
| if (!empty($currentLine)) { | |
| $lines[] = $currentLine; | |
| } | |
| } | |
| $lines = $this->wrapTextToLines($qrTitle, self::FONT_SIZE, $availableTextWidth); |
|
|
||
| // Crear archivo temporal | ||
| $extension = ($mimeType === 'image/png') ? '.png' : '.jpg'; | ||
| $tempFile = tempnam(sys_get_temp_dir(), 'qr_') . $extension; |
There was a problem hiding this comment.
Using tempnam() with a predictable prefix could potentially lead to temporary file conflicts or security issues. Consider using a more unique prefix that includes the process ID or a random component.
| $tempFile = tempnam(sys_get_temp_dir(), 'qr_') . $extension; | |
| $tempFile = tempnam(sys_get_temp_dir(), 'qr_' . uniqid()) . $extension; |
| $imageData = base64_decode($base64Data); | ||
|
|
||
| // Verificar si la decodificación fue exitosa | ||
| if ($imageData === false) { |
There was a problem hiding this comment.
When base64_decode() fails, the method silently returns without any indication of the failure. Consider logging this error or providing feedback about the invalid base64 data.
| if ($imageData === false) { | |
| if ($imageData === false) { | |
| error_log('Failed to decode base64 data for QR image. Data: ' . substr($base64Data, 0, 50) . '...'); |
Necesitamos una opción para añadir imágenes fijas de QRs fijas en los pdf.
¿Cómo has probado los cambios?
Toda modificación debe haber sido mínimamente probada. Marca o describe las pruebas que has realizado: